﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace rFDevLib {

    public class rF2Engine {
        double Clamp( double min, double n, double max ) {
            return Math.Max( min, Math.Min( max, n ) );
        }

        public struct RPMBase {
            public double rpm, coast, torque, idleFunc, presPower;

            public RPMBase( double rpm = 0.0, double coast = 0.0, double torque = 0.0, double idleFunc = 0.0, double presPower = 1.0 ) {
                this.rpm = rpm;
                this.coast = coast;
                this.torque = torque;
                this.idleFunc = idleFunc;
                this.presPower = presPower;
            }

            public override string ToString() {
                return "RPMBase=(" +
                    rpm.ToString( new CultureInfo( "en-US" ) ) + ", " +
                    coast.ToString( new CultureInfo( "en-US" ) ) + ", " +
                    torque.ToString( new CultureInfo( "en-US" ) ) + ", " +
                    idleFunc.ToString( new CultureInfo( "en-US" ) ) + ", " +
                    presPower.ToString( new CultureInfo( "en-US" ) ) + ")";
            }
        }

        public struct VolumeFrac {
            public List<double> values;
            public int resolution;

            private double maxWanted, maxAvailable;

            /*public VolumeFrac() {
                values = new List<double>();
                resolution = 16;
            }*/

            public VolumeFrac( double in_maxWanted, double in_maxAvailable ) {
                resolution = 16;
                values = new List<double>();
                maxWanted = in_maxWanted;
                maxAvailable = in_maxAvailable;
                FillIn( maxWanted, maxAvailable );
            }

            public void FillIn( double in_maxWanted, double in_maxAvailable ) {
                maxWanted = in_maxWanted;
                maxAvailable = in_maxAvailable;
                double throttlePos;
                values.Add( 0.0 );
                for ( int i = 1; i < resolution; i++ ) {
                    throttlePos = (double)i / (double)(resolution - 1);

                    double throttleOpen = 1.0 - Math.Cos( throttlePos * Math.PI * 0.5 );

                    values.Add( Math.Round( Math.Min( (maxAvailable * throttleOpen) / maxWanted, 1.0 ), 3 ) );
                }
            }

            public double GetThrottlePosForFrac( double opening ) {
                //double r = Math.Round( Math.Acos( 1.0 - (maxWanted / maxAvailable) ) * opening * 2.0 / Math.PI, 3 );

                if ( maxWanted < double.Epsilon || opening < double.Epsilon) return 0.0;

                int upperIndex = -1;

                for ( int i = 1; i <= 15; i++ ) {
                    if ( values[i] > opening ) {
                        upperIndex = i;
                        i = 15;
                    }
                }

                double dif = values[upperIndex] - values[upperIndex - 1];
                double inc = (opening - values[upperIndex - 1]) / dif;
                double ind = ((double)upperIndex / 15.0) + (inc / 15.0);

                return Math.Round(ind, 3);
            }

            public override string ToString() {
                string ret = "VolumeFract=(";
                for ( int i = 0; i < values.Count; i++ ) {
                    //ret += values[i].ToString();
                    ret += values[i].ToString( "f3", new CultureInfo( "en-US" ) );
                    if ( i + 1 < values.Count ) ret += ", ";
                }
                ret += ")";
                return ret;
            }
        }

        public struct MixtureFrac {
            public List<double> values;
            public int resolution;

            public MixtureFrac( int Res = 4 ) {
                values = new List<double>();
                resolution = 4;

                for ( int i = 0; i < resolution; i++ )
                    values.Add( 1.0 );
            }

            public override string ToString() {
                string ret = "MixtureFract=(";
                for ( int i = 0; i < values.Count; i++ ) {
                    ret += values[i].ToString( new CultureInfo( "en-US" ) );
                    if ( i + 1 < values.Count ) ret += ", ";
                }
                ret += ")";
                return ret;
            }
        }

        List<RPMBase> m_torqueCurve = new List<RPMBase>();
        List<VolumeFrac> m_volume = new List<VolumeFrac>();
        List<MixtureFrac> m_mixture = new List<MixtureFrac>();

        rF1Engine m_refEngine;

        public List<rFParam> m_params;
        public double chokeRPM = 10000.0; // rpm
        public double stoich = 0.073; // Stoichiometric Ratio/FAR
        public double fuelDensity = 0.74; // kg/l
        public bool preserveTorqueCurve = true;

        public string GetPowerInfo() {
            double maxhp = 0.0; // double.MinValue;
            double hp = 0.0;
            double rpm = 0.0;

            for ( int i = 0; i < m_torqueCurve.Count; i++ ) {
                RPMBase a = m_torqueCurve[i];
                hp = (a.rpm * (a.coast + ((a.torque - a.coast) * m_volume[i].values.Last()))) / (1.35581795 * 5252.0);

                if ( hp > maxhp ) {
                    maxhp = hp;
                    rpm = a.rpm;
                }
            }

            return maxhp.ToString( "F2" ) + "hp @ " + Math.Floor( rpm ).ToString() + "rpm";
        }

        public void BuildTorqueCurve( rF1Engine rF1Ref ) {
            //if ( rF1Ref.m_params.Count() == 0 &&
            //    rF1Ref.m_torqueCurve.Count() == 0 )
            //    return;

            chokeRPM = Math.Max( chokeRPM, 1000 );
            stoich = Math.Max( stoich, 0.001 );
            fuelDensity = Math.Max( fuelDensity, 0.001 );

            m_refEngine = rF1Ref;

            m_torqueCurve.Clear();
            m_volume.Clear();
            m_mixture.Clear();

            double idleThrottle = 0.0;
            double idleRange1 = 0.0, idleRange2 = 0.0;

            foreach ( rFParam a in m_refEngine.m_params ) {
                if ( a.name == "IdleThrottle" )
                    idleThrottle = a.GetDouble();
                if ( a.name == "IdleRPMLogic" ) {
                    idleRange1 = a.GetDouble( 0 );
                    idleRange2 = a.GetDouble( 1 );
                }
            }

            double idleRange = idleRange2 - idleRange1;
            if ( idleRange == 0.0 ) {
                idleThrottle = 0.0;
                idleRange = 1.0;
            }

            foreach ( RPMData a in rF1Ref.m_torqueCurve ) {
                double rps = a.RPM / 60.0;

                m_volume.Add( new VolumeFrac( a.RPM, chokeRPM ) );
                m_mixture.Add( new MixtureFrac( 4 ) );

                double torque = a.Torque;
                if ( preserveTorqueCurve ) {
                    //torque /= m_volume.Last().values.Last();
                    //torque /= Math.Min( 1.0, chokeRPM / a.RPM );
                    double dif = a.Torque - a.Coast;
                    torque = (dif / Math.Min( 1.0, chokeRPM / a.RPM )) + a.Coast;
                    double asd = 0.0;
                }

                double rpmIdleEff = Math.Max( Math.Min( (-a.RPM + idleRange2) / idleRange, 1.0 ), 0.0 );

                m_torqueCurve.Add( new RPMBase( a.RPM, a.Coast, Math.Min( torque, double.MaxValue ), m_volume.Last().GetThrottlePosForFrac( rpmIdleEff * idleThrottle ) ) );
            }

            //
            // Brake mapping
            //
            m_params = new List<rFParam>();

            rFParam brakeMapRange = new rFParam( m_refEngine.GetParam( "EngineBrakingMapRange" ).ToString() );
            double RPMStep = m_refEngine.GetMaxRPM() * 0.1;
            double maxBrakeMapThrottle = brakeMapRange.GetDouble( 0 ) + (brakeMapRange.GetDouble( 1 ) * (brakeMapRange.GetDouble( 2 ) - 1));

            rFParam brakeLimit = new rFParam( " " );
            brakeLimit.name = "EngineBrakingLimit";

            for ( int i = 0; i <= 10; i++ ) {
                brakeLimit.values.Add( m_refEngine.GetTorque( (double)i * RPMStep, maxBrakeMapThrottle ).ToString( "f0" ) );
            }

            brakeMapRange.values[1] = (1.0 / (brakeMapRange.GetDouble( 2 ) - 1.0)).ToString( "f3" );
            rFParam brakingParams = new rFParam( " " );
            brakingParams.name = "EngineBrakingParams";
            brakingParams.values.Add( "2" );
            brakingParams.values.Add( (m_refEngine.GetMaxRPM() * 0.1).ToString( "f0" ) );

            m_params.Add( brakingParams );
            m_params.Add( brakeLimit );
            m_params.Add( brakeMapRange );

            return;
        } // void BuildTorqueCurve

        public void WriteToStream( System.IO.StreamWriter w ) {
            // Comment header, if applicable

            int j = 0;
            while ( m_refEngine.m_params[j].ToString().Substring( 0, Math.Min( 2, m_refEngine.m_params[j].ToString().Length ) ) == "//" ) {
                w.WriteLine( m_refEngine.m_params[j].ToString() );
                j++;
            }

            // Engine data itself

            w.WriteLine( "ReferenceConditions=(101325, 1.225, " + stoich.ToString( new CultureInfo( "en-US" ) ) + ")" );
            for ( int i = 0; i < m_torqueCurve.Count; i++ ) {
                w.WriteLine( m_torqueCurve[i].ToString() );
                w.WriteLine( m_volume[i].ToString() );
                w.WriteLine( m_mixture[i].ToString() );
            }

            w.WriteLine( "FuelDensity=" + fuelDensity.ToString( new CultureInfo( "en-US" ) ) );

            w.WriteLine( "FuelAirMixtureTable=(0.0, 0.091)    // start and step size of fuel/air ratio (normalized relative to reference mixture) for following table:" );
            w.WriteLine( "FuelAirMixtureEffects=(0.00, 0.00) // torque multiplier, exhaust gas temperature multiplier" );
            w.WriteLine( "FuelAirMixtureEffects=(0.10, 0.11)" );
            w.WriteLine( "FuelAirMixtureEffects=(0.20, 0.22)" );
            w.WriteLine( "FuelAirMixtureEffects=(0.30, 0.33)" );
            w.WriteLine( "FuelAirMixtureEffects=(0.40, 0.44)" );
            w.WriteLine( "FuelAirMixtureEffects=(0.50, 0.55)" );
            w.WriteLine( "FuelAirMixtureEffects=(0.60, 0.66)" );
            w.WriteLine( "FuelAirMixtureEffects=(0.70, 0.77)" );
            w.WriteLine( "FuelAirMixtureEffects=(0.80, 0.88)" );
            w.WriteLine( "FuelAirMixtureEffects=(0.89, 0.99)" );
            w.WriteLine( "FuelAirMixtureEffects=(0.98, 1.00)" );
            w.WriteLine( "FuelAirMixtureEffects=(1.00, 0.97)" );
            w.WriteLine( "FuelAirMixtureEffects=(0.97, 0.90)" );
            w.WriteLine( "FuelAirMixtureEffects=(0.85, 0.80)" );
            w.WriteLine( "FuelAirMixtureEffects=(0.65, 0.60)" );
            w.WriteLine( "FuelAirMixtureEffects=(0.40, 0.40)" );
            w.WriteLine( "FuelAirMixtureEffects=(0.10, 0.10)" );
            w.WriteLine( "FuelAirMixtureEffects=(0.00, 0.00)" );

            for ( int i = j; i < m_refEngine.m_params.Count(); i++ ) {
                rFParam a = m_refEngine.m_params[i];
                if ( a.name != "IdleRPMLogic" && a.name != "IdleThrottle" ) {
                    if ( a.name == "EngineBrakingMapRange" ) {
                        foreach ( rFParam b in m_params ) {
                            w.WriteLine( b.ToString() );
                        }
                        //w.WriteLine( a.ToString() );
                    } else {
                        w.WriteLine( a.ToString() );
                    }
                }
            }
        }

        public void DrawGraphic( TorqueCurveGraphic gfx ) {
            double torqueMin = double.MaxValue,
                torqueMax = double.MinValue,
                rpmMax = double.MinValue;

            for ( int i = 0; i < m_torqueCurve.Count; i++ ) {
                RPMBase a = m_torqueCurve[i];
                torqueMin = Math.Min( torqueMin, a.coast );
                torqueMax = Math.Max( torqueMax, a.torque * m_volume[i].values.Last() );
                rpmMax = Math.Max( rpmMax, a.rpm );
            }

            gfx.NewGraphic( torqueMin, torqueMax, rpmMax );

            List<double> rpm = new List<double>();

            foreach ( RPMBase a in m_torqueCurve )
                rpm.Add( a.rpm );

            for ( int i = 0; i < 16; i++ ) {
                // Throttle should be red at max, blue at min
                System.Drawing.Color c = System.Drawing.Color.FromArgb( i * 15, 0, 255 - (i * 15) );

                List<double> torque = new List<double>();
                for ( int j = 0; j < m_torqueCurve.Count; j++ ) {
                    double diff = m_torqueCurve[j].torque - m_torqueCurve[j].coast;

                    torque.Add( m_torqueCurve[j].coast + (diff * m_volume[j].values[i]) );
                }

                gfx.DrawTorqueCurve( rpm, torque, c );
            }

            foreach ( rFParam a in m_refEngine.m_params ) {
                if ( a.name == "RevLimitRange" ) {
                    double rpmLimit = a.GetDouble( 0 ) + (a.GetDouble( 1 ) * (a.GetDouble( 2 ) - 1.0));
                    gfx.AddRPMLimit( rpmLimit );
                }
            }
        }


    }
}
